﻿@using System.Web.Optimization
@using Composite.C1Console.Security
@using Composite.Core
@using Composite.Web.Css.Less
@inherits RazorFunction

@functions {
    public override string FunctionDescription
    {
        get { return "Bundling and minification of the Style and Javascript files"; }
    }

    [FunctionParameter(Label = "Web Page", Help = "The web page to transform.")]
    public XhtmlDocument Page { get; set; }


    private XhtmlDocument BundlingAndMinification(XhtmlDocument page)
    {
        if (HttpContext.Current.IsDebuggingEnabled || UserValidationFacade.IsLoggedIn() || page.Descendants(Namespaces.AspNetControls + "marker").Any())
            return page;

        var pageBeforeTransformation = new XhtmlDocument(page);
        try
        {
            BundleScriptsAndMoveAtBottom(page);
            BundleStyles(page);
        }
        catch (Exception ex)
        {
            Log.LogError("Bundling and Minification", ex.Message);
            BundleTable.Bundles.Clear();
            return pageBeforeTransformation;
        }

        return page;
    }

    private void BundleScriptsAndMoveAtBottom(XhtmlDocument page)
    {
        var registeredIds = new List<string>();
        var registeredScripts = new List<string>();
        var registeredInlineScripts = new List<XElement>();

        var scriptElements = page.Descendants(Namespaces.Xhtml + "script").ToList();
        scriptElements.AddRange(page.Descendants("script"));

        foreach (var scriptElement in scriptElements)
        {
            var noMoveAttrib = scriptElement.Attribute("data-c1-nomove");
            if (noMoveAttrib != null)
            {
                noMoveAttrib.Remove();
                continue;
            }

            var id = (string)scriptElement.Attribute("id");
            var src = (string)scriptElement.Attribute("src");

            scriptElement.Remove();

            if (registeredIds.Any(f => f == id) || registeredScripts.Any(f => f == src))
            {
                continue;
            }
            if (string.IsNullOrEmpty(src))
            {
                registeredInlineScripts.Add(scriptElement);
                continue;
            }
            var bundleSkipAtr = scriptElement.Attribute("data-c1-nobundle");
            if (bundleSkipAtr != null)
            {
                bundleSkipAtr.Remove();
                registeredInlineScripts.Add(scriptElement);
                continue;
            }

            if (id != null) registeredIds.Add(id);

            var virtualPath = GetVirtualPath(src);
            if (!string.IsNullOrEmpty(virtualPath))
            {
                registeredScripts.Add(virtualPath);
            }
            else
            {
                page.Body.Add(scriptElement);
            }
        }

        var scriptsKey = string.Join(string.Empty, registeredScripts.ToArray()).GetHashCode();
        var bundleVirtualPath = "~/Bundles/Scripts" + scriptsKey;
        if (BundleTable.Bundles.GetBundleFor(bundleVirtualPath) == null)
        {
            var sb = new ScriptBundle(bundleVirtualPath);
            foreach (var source in registeredScripts)
            {
                sb.Include(source);
            }
            BundleTable.Bundles.Add(sb);
        }

        var bundlerUrl = BundleTable.Bundles.ResolveBundleUrl(bundleVirtualPath);
        var bundlerScriptElement = new XElement(Namespaces.Xhtml + "script", new XAttribute("src", bundlerUrl), new XAttribute("type", "text/javascript"));

        page.Body.Add(bundlerScriptElement);
        foreach (var inline in registeredInlineScripts)
        {
            page.Body.Add(inline);
        }

    }

    //TODO: implement Sass to Bundle
    private void BundleStyles(XhtmlDocument page)
    {
        var registeredIds = new List<string>();
        var registeredSources = new List<string>();

        var styleElements = page.Descendants(Namespaces.Xhtml + "link").Where(l => l.Attribute("rel") != null && l.Attribute("rel").Value == "stylesheet").ToList();

        foreach (var link in styleElements)
        {
            var bundleSkipAtr = link.Attribute("data-c1-nobundle");
            if (bundleSkipAtr != null)
            {
                bundleSkipAtr.Remove();
                continue;
            }
            
            var id = (string)link.Attribute("id");
            var href = (string)link.Attribute("href");

            var fileExtention = Path.GetExtension(href);
            if (fileExtention != ".css" && fileExtention != ".less") { continue; }

            if (registeredIds.Any(f => f == id) || registeredSources.Any(f => f == href))
            {
                link.Remove();
                continue;
            }

            if (id != null) registeredIds.Add(id);

            var virtualPath = GetVirtualPath(href);
            if (!string.IsNullOrEmpty(virtualPath))
            {
                registeredSources.Add(virtualPath);
                link.Remove();
            }
        }

        var pathKey = string.Join(string.Empty, registeredSources.ToArray()).GetHashCode();
        var bundleVirtualPath = "~/Bundles/Styles" + pathKey;
        if (BundleTable.Bundles.GetBundleFor(bundleVirtualPath) == null)
        {
            var sb = new StyleBundle(bundleVirtualPath);
            foreach (var source in registeredSources)
            {
                sb.Include(Path.GetExtension(source) == ".less" ? CompressFiles.CompressLess(source) : source, new CssRewriteUrlTransform());
            }
            BundleTable.Bundles.Add(sb);
        }

        var bundlerUrl = BundleTable.Bundles.ResolveBundleUrl(bundleVirtualPath);
        var bundlerStylesElement = new XElement(Namespaces.Xhtml + "link", new XAttribute("href", bundlerUrl), new XAttribute("type", "text/css"), new XAttribute("rel", "stylesheet"));

        page.Head.Add(bundlerStylesElement);
    }


    private string GetVirtualPath(string url)
    {
        if (string.IsNullOrEmpty(url)) return null;
        if (url.StartsWith("//"))
        {
            url = "http:" + url;
        }
        Uri uri;
        if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri) ||
            (uri.IsAbsoluteUri && uri.Host != Context.Request.Url.Host))
        {
            return null;
        }

        if (uri.IsAbsoluteUri)
        {
            url = uri.AbsolutePath;
        }
        return url.StartsWith("~") ? url : "~" + url;
    }
}

@Html.Raw(BundlingAndMinification(Page))
